<?php

namespace App\Core;
use App\Core\Registry as Registry;
use App\Core\HttpErrorHandler as HttpErrorHandler;

/**
 * Clase enrutadora.
 *
 * Esta clase se encarga de obtener los segmentos
 * url enviados mediante el metodo de peticion GET.
 *
 * @todo  Convetir a singleton ?
 */
class Router{

	/**
	 * Instancia del registro global de Framawork.
	 * @var App\Registry
	 */
	private $registry;
	/**
	 * Argumentos pasados a la URL.
	 * @var string[]
	 */
	private $arguments;
	/**
	 * La ruta capturada por URL.
	 * @var string
	 */
	private $route;

	/**
	 * Construye una instancia del router.
	 * 
	 * @param Registry $registry Una instancia del registro
	 *                           global de Framawork
	 * @return void
	 */
	public function __construct(Registry $registry){
		$this->registry = $registry;
		$this->arguments = Array();
	}

	/**
	 * Captura y decodifica la ruta enviada por URL.
	 * Una vez identificados todos los segmentos, invoca al controlador
	 * correspondiente, y le envia el mensaje especificado en la URL
	 * con sus respectivos parametros.
	 * 
	 * @return void
	 */
	public function routing(){
		$this->route = (empty($_GET['route'])) ? '' : $_GET['route'];

		$controller = $this->decodeController();
		$this->validateController($controller);

		// Incluyo al controlador.
		include_once $controller->filename;

		// Instancio el controlador.
		$class = "Src\Controller\\" . $controller->name;
        $controller->instance = new $class($this->registry);

        $action = $this->decodeAction();
        $this->validateAction($controller, $action);

        $arguments = $this->decodeArguments();
        $this->validateArguments($class, $action, $arguments);

        // Realizo la invocacion al metodo bindeando los parametros.
        call_user_func_array(array($controller->instance, $action), $arguments);
	}

	/**
	 * Decodifica la ruta capturada por URL, y retorna toda la informacion
	 * necesaria para cargar un controlador.
	 * 
	 * @return StdClass()
	 */
	private function decodeController(){
		$data = new \StdClass();

		if(empty($this->route)){
			$data->filename = "Src/Controller/" . FW_DEFAULT_CONTROLLER . ".php";
			$data->name = FW_DEFAULT_CONTROLLER;
		}else{
			$segments = explode('/', $this->route);
			$data->filename = "Src/Controller/" . ucfirst($segments[0]) . ".php";
			$data->name = ucfirst($segments[0]);
		}

		$data->instance = null;
		return $data;
	}

	/**
	 * Decodifica la ruta capturada por URL, y retorna el nombre de la accion
	 * definida en el controlador retornado por Self::decodeController()
	 * 
	 * @return string
	 */
	private function decodeAction(){
		$segments = explode('/', $this->route);
		if(empty($segments[1])){
			return FW_DEFAULT_ACTION;
		}else{
			return ucfirst($segments[1]);
		}		
	}

	/**
	 * Decodifica la ruta capturada por URL, y retorna un arreglo
	 * con los parametros a enviar a metodo del controlador retornado
	 * por Self::decodeAction()
	 * 
	 * @return string[]
	 */
	private function decodeArguments(){
		$segments = explode('/', $this->route);
		if(count($segments) > 2){
	        return array_slice($segments, 2);
	    }else{
	    	return array();
	    }
	}

	/**
	 * Valida que el controlador solicitado exista.
	 * En caso de no existir, lanzara un error 404
	 * o bien, un error para ser capturado por PHP.
	 * 
	 * @param  StdClass $controller Informacion devuelta por 
	 * Self::decodeController()
	 * @return void
	 */
	private function validateController($controller){
		if(!is_readable($controller->filename)){
			if(FW_DEVELOPMENT){
				$ns = "Src\Controller\\" . $controller->name;
				$message = sprintf(FW_FILE_NOT_FOUND, $ns);
				throw_error($message);
			}else{
				HttpErrorHandler::pageNotFound();	
			}
		}
	}

	/**
	 * Valida que el controlador solicitado posea el metodo
	 * requerido.
	 * En caso de no existir, lanzara un error 404
	 * o bien, un error para ser capturado por PHP.
	 * 
	 * @param  StdClass $controller Informacion devuelta por 
	 * Self::decodeController()
	 * @param  string $action El nombre del metodo.
	 * @return void
	 */
	private function validateAction($controller, $action){
		if(!is_callable(array($controller->instance, $action))){
        	if(FW_DEVELOPMENT){
        		$ns = "Src\Controller\\" . $controller->name . "::$action()";
        		$message = sprintf(FW_INVALID_ACTION, $ns);
				throw_error($message);
        	}else{
        		HttpErrorHandler::pageNotFound();
        	}
        }
	}

	/**
	 * Valida que la cantidad de argumentos solicitados 
	 * sea la correcta.
	 * En caso de no existir, lanzara un error 505
	 * o bien, un error para ser capturado por PHP.
	 * 
	 * @param string $class El nombre de la clase.
	 * @param string $action El nombre del metodo.
	 * @param string[] $arguments Un arreglo de argumentos.
	 * @return void
	 */
	private function validateArguments($class, $action, $arguments){
		$reflection =  new \ReflectionMethod($class,$action);
        $arguments_needed = $reflection->getNumberOfParameters();
        
        if($arguments_needed != count($arguments)){
        	if(FW_DEVELOPMENT){
        		$ns = "Src\Controller\\$class::$action()";
        		$message = sprintf(FW_INVALID_ARGS, $ns);
				throw_error($message);
        	}else{
        		HttpErrorHandler::internalServerError();
        	}	
        }
	}
}
?>